Preserve list type in ConstantArrayType::spliceArray when all keys are integers#5480
Merged
VincentLanglet merged 1 commit intophpstan:2.1.xfrom Apr 15, 2026
Conversation
…are integers - When `array_splice` is called on a constant array with only integer keys and a non-constant replacement, the builder degradation caused `isList` to be incorrectly set to `No` (via non-null key in `setOffsetValueType`). - After building the result, check whether all keys in the original constant array were integers. If so, intersect the result with `AccessoryArrayListType` since `array_splice` always re-indexes integer keys. - The fix is applied per-replacement-variant before the final union, so it correctly handles cases where the replacement is a union of arrays. - Added regression test covering constant arrays with integer keys (the reported bug), list replacements, and string-key arrays (already correct). - Probed `sliceArray`, `IntersectionType::spliceArray`, `AccessoryArrayListType::spliceArray`, sort functions, and `ConstantTypeHelper` for analogous issues — none found in the same pattern.
VincentLanglet
approved these changes
Apr 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When
array_spliceis called on a constant array with only integer keys and a non-constant replacement array, PHPStan incorrectly inferred the result asnon-empty-array<int<0, max>, string>instead ofnon-empty-list<string>. Sincearray_splicealways re-indexes integer keys, the result should be recognized as a list.Changes
src/Type/Constant/ConstantArrayType.php,spliceArray()method:$allKeysIntegercheck before the main loop to determine if the original constant array has only integer keysAccessoryArrayListTypeto ensure list-ness is preservedtests/PHPStan/Analyser/nsrt/bug-14472.phpwith three cases:Root cause
In
ConstantArrayType::spliceArray(), when the replacement array is not aConstantArrayType, theConstantArrayTypeBuilderis degraded viadegradeToGeneralArray(), and thensetOffsetValueType()is called with the replacement's key type (non-null). In the degraded builder path, any non-null key insetOffsetValueTypesets$this->isList = TrinaryLogic::createNo(), which causes the list property to be lost.The fix adds a post-processing step: after the builder produces the result, if the original constant array had only integer keys,
AccessoryArrayListTypeis added to the result. This mirrors the same logic already present inArrayType::spliceArray()(line 546-548).Analogous cases probed
ConstantArrayType::sliceArray()— usesnullfor auto-increment keys in non-degraded builder, correctly preserves list-ness. No issue.IntersectionType::spliceArray()— delegates to each member type includingAccessoryArrayListType::spliceArray()which returns$this. No issue.AccessoryArrayListType::spliceArray()— returns$this. No issue.sort,rsort,usort) — handled inFuncCallHandlerby directly constructing list types. No builder degradation involved. No issue.ConstantTypeHelper::getTypeFromValue()— has a similar pattern with degradation for large arrays (>256 elements), but affects a much rarer edge case and uses a different code path. Not fixed in this PR.Test
tests/PHPStan/Analyser/nsrt/bug-14472.php: NSRT test withassertTypecalls verifying:array_spliceon['last_name', 'first_name', ...]with non-constant replacement producesnon-empty-list<string>array_spliceon['a', 'b', 'c']with list replacement producesnon-empty-list<string>array_spliceon string-keyed array preserves exact constant array type (no regression)Fixes phpstan/phpstan#14472